#include "LRAnalyzer.hpp"

bool LRAnalyzer::isNoneTerminal(std::string &s) {
  if (!terminals.count(s) && s != "") {
    return true;
  }
  return false;
}

bool LRAnalyzer::isTerminal(std::string s) { return terminals.count(s); }

int LRAnalyzer::whereItemExisted(
    std::vector<ProjectSet> &pjSetVec,
    const std::unordered_map<std::string, ProjectItem> &kernelMap) {
  for (int i = 0; i < pjSetVec.size(); ++i) {
    auto &targetKernelSet = pjSetVec[i].kernelSet;
    if (targetKernelSet.size() != kernelMap.size()) {
      continue;
    }
    bool isSame = true;
    for (const auto &item : targetKernelSet) {
      if (!kernelMap.count(item)) {
        isSame = false;
        break;
      }
    }
    if (isSame) {
      return i;
    }
  }
  return -1;
}

std::unordered_map<std::string, ProjectItem>
LRAnalyzer::getLeftBeginWith(std::string left) {
  std::unordered_map<std::string, ProjectItem> res;
  for (int i = 0; i < defineExpresses.size(); ++i) {
    if (defineExpresses[i].leftItem == left) {
      auto pjItem = ProjectItem{i, 0};
      res[pjItem.getId()] = pjItem;
    }
  }
  return res;
}

ProjectSet LRAnalyzer::buildProjectSetFromKernel(
    std::unordered_map<std::string, ProjectItem> &kernelMap) {
  ProjectSet res;
  for (const auto &kv : kernelMap) {
    res.itemKVs.insert(kv);
    res.kernelSet.insert(kv.first);
  }

  auto changed = true;
  while (changed) {
    changed = false;
    auto beforeSize = res.itemKVs.size();
    for (auto kv : res.itemKVs) {
      auto pjItem = kv.second;
      auto rightItem = defineExpresses[pjItem.defineExprIndex].rightItem;
      if (pjItem.pointPos < rightItem.size() &&
          isNoneTerminal(rightItem[pjItem.pointPos])) {
        auto toInsert = getLeftBeginWith(rightItem[pjItem.pointPos]);
        res.itemKVs.insert(toInsert.begin(), toInsert.end());
      }
    }
    if (res.itemKVs.size() > beforeSize) {
      changed = true;
    }
  }
  return res;
}

void LRAnalyzer::appendProjectSet(std::vector<ProjectSet> &pjSetVec,
                                  int targetIndex) {
  std::unordered_map<std::string, std::unordered_map<std::string, ProjectItem>>
      convertionKernelMap;
  for (auto kv : pjSetVec[targetIndex].itemKVs) {
    auto &pjItem = kv.second;
    auto &rightItem = defineExpresses[pjItem.defineExprIndex].rightItem;
    if (rightItem.front() == "" || pjItem.pointPos >= rightItem.size()) {
      continue;
    }

    auto newItem = ProjectItem{pjItem.defineExprIndex, pjItem.pointPos + 1};
    std::string convertion = rightItem[pjItem.pointPos];
    convertionKernelMap[convertion].insert({newItem.getId(), newItem});
  }

  for (auto kv : pjSetVec[targetIndex].itemKVs) {
    auto &pjItem = kv.second;
    auto &rightItem = defineExpresses[pjItem.defineExprIndex].rightItem;

    // printf("projectItem: %d %d\n", pjItem.defineExprIndex,
    // pjItem.pointPos);
    if (rightItem.front() == "" || pjItem.pointPos >= rightItem.size()) {
      continue;
    }

    auto newItem = ProjectItem{pjItem.defineExprIndex, pjItem.pointPos + 1};
    auto convertion = rightItem[pjItem.pointPos];

    auto kernelMap = convertionKernelMap[convertion];
    auto idx = whereItemExisted(pjSetVec, kernelMap);
    if (idx == -1) {
      auto newPjSet = buildProjectSetFromKernel(kernelMap);
      pjSetVec.push_back(newPjSet);
      idx = pjSetVec.size() - 1;
    }
    // printf("alive\n");
    // printf("%d %s %d\n", pjItem.pointPos,
    //        rightItem[pjItem.pointPos].c_str(), idx);
    pjSetVec[targetIndex].convertion[convertion] = idx;
    // for (const auto &kv : pjSet.convertion) {
    //   printf("%s %d\n", kv.first.c_str(), kv.second);
    // }
  }
}

std::vector<ProjectSet> LRAnalyzer::getProjectSets() {
  std::vector<ProjectSet> projectSetVec;

  std::unordered_map<std::string, ProjectItem> initialKernelMap;
  auto startItem = ProjectItem{0, 0};
  initialKernelMap.insert({startItem.getId(), startItem});
  projectSetVec.push_back(buildProjectSetFromKernel(initialKernelMap));

  for (int i = 0; i < projectSetVec.size(); ++i) {
    // printf("project %d:\n", i);
    appendProjectSet(projectSetVec, i);
  }
  return projectSetVec;
}
